package com.cdplife.ci.core.interceptor;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

import com.cdplife.ci.core.common.annotion.DataAccess;
import com.cdplife.ci.core.common.constant.AuthDimensionEnum;
import com.cdplife.ci.core.shiro.ShiroKit;
import com.cdplife.ci.core.util.ReflectUtil;
import com.cdplife.ci.core.util.StringUtil;
//import com.cdplife.ci.modular.system.service.impl.AuthRelationServiceImpl;
import com.cdplife.ci.modular.system.service.impl.AuthRelationServiceImpl;

import cn.hutool.core.convert.Convert;
import cn.stylefeng.roses.core.util.SpringContextHolder;

/**
 * 
   *  数据权限拦截器
 * @author Shilin.Qu
 *
 */
@Intercepts({
	@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class })
})//按需配置
public class DataAccessInterceptor implements Interceptor {
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("======进入 DataAccessInterceptor拦截器======");
		
		if(invocation.getTarget() instanceof StatementHandler) {
			RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
            StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
            // 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
            MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
            
            // 若在对应的查询Mapper上没有DataAccess注解或者注解Value不对，则迅速过掉拦截器
            DataAccess dataAccess = getPermissionByDelegate(mappedStatement);
            if(dataAccess == null || dataAccess.value().length == 0 || dataAccess.key().length == 0){
            	System.out.println("======无权限，直接过滤======");
                return invocation.proceed();
            }
            
            // 获取当前登录用户的角色
            List<Integer> roleList = ShiroKit.getUser().getRoleList();            
            // 获取当前角色具有的权限,取角色的权限集合最大集
            AuthRelationServiceImpl authRelationService = SpringContextHolder.getBean(AuthRelationServiceImpl.class);
            Map<Integer,List<Long>> maxAuthorityMap = authRelationService.getMaxDimensionAuthority(roleList);            
            // 判断当前dataAccess是否在当前角色具有的权限范围内，不存在的话，则直接invocation.proceed()
            List<Long> dimensionList = maxAuthorityMap.get(AuthDimensionEnum.PARENT.getPrefix());
            
            // 若不完全是子集，不加权限，则迅速过掉拦截器
            if(dimensionList.size() == 0 || !dimensionList.containsAll(Arrays.asList(Convert.toLongArray(dataAccess.value())))) {
            	System.out.println("======无权限，直接过滤======");
            	return invocation.proceed();
            }
           
            BoundSql boundSql = delegate.getBoundSql();
            
            System.out.println("当前拦截的SQL：" + boundSql.getSql());
            ReflectUtil.setFieldValue(boundSql, "sql", wrapperSql(boundSql.getSql(),dataAccess,maxAuthorityMap));
            System.out.println("重新包装的SQL：" + boundSql.getSql());
            return invocation.proceed();
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		
	}
	
	/**
	 * 权限sql包装
	 * @param sql
	 * @param dataAccess
	 * @param maxAuthorityMap
	 * @return
	 */
    protected String wrapperSql(String sql,DataAccess dataAccess,Map<Integer,List<Long>> maxAuthorityMap) {
        StringBuilder sbSql = new StringBuilder(sql);
        
        long[] dataAccessValues = dataAccess.value();
        String[] dataAccessKeys = dataAccess.key();
        
        StringBuilder where = new StringBuilder("");
        for(int i=0;i<dataAccessValues.length;i++) {
        	List<Long> valueList = null;
        	String wSql = "";
        	// 保险公司
            if(dataAccessValues[i] == AuthDimensionEnum.INSURANCE.getId()) {
            	valueList = maxAuthorityMap.get(AuthDimensionEnum.INSURANCE.getPrefix());
            	wSql = new StringBuilder(dataAccessKeys[i]).append(" in (" + StringUtil.listToString(valueList, ',') + ") ").toString();
            }
            
            // 客户
    		if(dataAccessValues[i] == AuthDimensionEnum.TENANT.getId()) {
    			valueList = maxAuthorityMap.get(AuthDimensionEnum.TENANT.getPrefix());  
    			wSql = new StringBuilder(dataAccessKeys[i]).append(" in (" + StringUtil.listToString(valueList, ',') + ") ").toString();
    		}

    		// 员工方案分组,暂不做处理
    		if(dataAccessValues[i] == AuthDimensionEnum.EMPLOYEE_GROUP.getId()) {
    			valueList = maxAuthorityMap.get(AuthDimensionEnum.TENANT.getPrefix());
    		} 
    		if(i == dataAccessValues.length-1) {
    			where.append(wSql);
    		}else {
    			where.append(wSql).append("and");
    		}
        }
        
        where = (where.toString() == "" ? where.append(" 1 = 1 ") : where);
        sbSql = new StringBuilder("select * from (").append(sbSql).append(" ) s  where ").append(where);
        return sbSql.toString();
    }

   /**
    * 根据 StatementHandler 获取 注解对象
    * @param mappedStatement
    * @return
    */
    public static DataAccess getPermissionByDelegate(MappedStatement mappedStatement){
    	DataAccess dataAccess = null;
        try {
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            String methodName = id.substring(id.lastIndexOf(".") + 1, id.length());
            final Class<?> cls = Class.forName(className);
            final Method[] method = cls.getMethods();
            for (Method me : method) {
                if (me.getName().equals(methodName) && me.isAnnotationPresent(DataAccess.class)) {
                	dataAccess = me.getAnnotation(DataAccess.class);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return dataAccess;
    }
}
